#pragma once
#include "Util.hpp"
#include "Http.hpp"

#define DEFAULT_TIMEOUT 10

class HttpServer{
private:
    using Handler = std::function<void(HttpRequest&, HttpResponse*)>;
    using HandlerRoute = std::vector<std::pair<std::regex, Handler>>;
private:
    /*错误的处理*/
    void ErrorHandler(const HttpRequest& req, HttpResponse* resp){
        //其实就是搞一个带有404界面正文给应答
        std::string out, path_404 = _base_root_dir;
        path_404 = path_404 + ((_base_root_dir.back() == '/') ? "" : "/") + std::string("404.html");
        Util::ReadFromFile(path_404, &out);
        resp->SetContent(out, "text/html");
    }

    /*判断是否是请求静态资源*/
    bool IsStaticResource(const HttpRequest& req){
        //1.设置了站点根目录
        if(_base_root_dir.empty()) return false;
        //2.请求方法必须是GET or HEAD
        if(req._method != "GET" && req._method != "HEAD") return false;
        //3.请求资源的路径合法
        if(!Util::IsValidPath(req._url)) return false;
        //4.请求资源存在 但是今天这里有可能单纯是一个/
        std::string reqstr = _base_root_dir + req._url;
        if(reqstr.back() == '/') reqstr += "index.html";
        if(!Util::IsRegularFile(reqstr)) return false;
        return true;
    }

    /*对于静态资源的获取和操作*/
    void HandlerStaticResource(HttpRequest& req, HttpResponse* resp){
        std::string req_str = _base_root_dir + req._url;
        if(req_str.back() == '/') req_str += "index.html";
        bool ret = Util::ReadFromFile(req_str, &resp->_text);
        if(!ret) return;
        //读取成功，就要设置一下类型字段
        std::string mime = Util::ExtMime(req_str);
        resp->SetHeaders("Content-Type", mime);
    }

    /*对于动态请求的分发*/
    void DisPatchToRoute(HttpRequest& req, HttpResponse* resp, HandlerRoute& route){
        //在路由表中进行检索，查看是否有对应资源的处理函数，没有就返回404 Not Found
        //HandlerRoute是一个个的正则表达式&处理函数
        for(auto& it : route){
            const std::regex& re = it.first;
            const Handler& functor = it.second;
            bool ret = std::regex_match(req._url, req._matches, re);
            if(!ret) continue;
            else return functor(req, resp);
        }
        resp->_statu_code = 404;
        ErrorHandler(req, resp);
    }

    /*对此次请求做分辨操作，判断是静态资源的请求还是动态交互 进行路由操作*/
    void Route(HttpRequest& req, HttpResponse* resp){
        //1.先判断是不是静态资源的请求
        if(IsStaticResource(req)) return HandlerStaticResource(req, resp); 
        //2.反之进行动态请求处理
        if(req._method == "HEAD" || req._method == "GET") return DisPatchToRoute(req, resp, _get_route);
        else if(req._method == "PUT") return DisPatchToRoute(req, resp, _put_route);
        else if(req._method == "POST") return DisPatchToRoute(req, resp, _post_route);
        else if(req._method == "DELETE") return DisPatchToRoute(req, resp, _delete_route);        
        //3.二者都不是 -> 那就直接返回405 代表操作不合法
        resp->_statu_code = 405;    ///Method Not Allowed
    }

    /*对于上下文的设置 其实就是新链接到来的时候，设置一下连接内对应的上下文类型，以便后序操作不会出错！*/
    void OnConnected(const ConnPtr& conn){
        LOG(LogLevel::DEBUG) << "NEW CONNECTION : " << conn.get(); 
        conn->SetContext(HttpContext());
    }

    /*针对于Http请求进行分析*/
    void OnMessage(const ConnPtr& conn, Buffer* buf){
        while(buf->ReadableSize() > 0){
            //1.获取Http的上下文 然后进行处理
            HttpContext* context = conn->GetContext()->Get<HttpContext>();
            //2.获取结束后，拼凑成一个HttpRequest
            //  2.1.可能处理会出错
            //  2.2.可能没处理完
            //  2.3.可能已经处理完了
            context->RecvHttpRequest(buf);
            HttpRequest& req = context->Request();
            HttpResponse resp(context->RespStatu());    //提前准备好应答
            if(context->RespStatu() >= 400){
                //此时说明处理出错了，我们进行错误的处理
                ErrorHandler(req, &resp);
                WriteResponseAndSend(conn, req, resp);
                context->Reset();   
                conn->Shutdown();
                buf->Clear();
                return;        

                //这个位置会有无限套娃的情况！！！！！！！！！！！！！！！
                //如果某一次出错了，但是我们这的ShutDown是先判断是否还有数据，有的话处理后再来发送！
                //万一真的很不巧：出错了，但是还有数据，WriteResponseAndSend(conn, req, resp);会调用conn的Send
                //但是，有一个很大的问题，此时的状态被设置为了HTTP_RECV_ERROR，那么还有数据，就还得处理数据，又回调到OnMessage
                //但是，现在的状态是HTTP_RECV_ERROR，RecvHttpRequest这里是完全读不出任何的数据的！

                //这就导致：接收缓冲区一直存留数据，但是一直出错，一直向conn的输出缓冲区写入 -> 数据一直读不出来，一直触发OnMessage -> conn->Send的时候一直扩容  -> 内部不足会崩！
                //所以正确做法：
                //  1.一旦出错，也就别玩了，直接清空缓冲区数据
                //  2.要不然就重置上下文，此时状态会重新变成HTTP_RECV_LINE，肯定有机会读完的！
                //当然，两步一起做也是可以的！
                
            }
            if(context->RecvStatu() != HTTP_RECV_OVER) return;

            //走到这里就说明此时接收到了一个完整的Http请求，先把它拿出来，然后进行业务处理
            //3.业务处理
            Route(req, &resp);
            //4.组织HttpResponse，然后发送
            WriteResponseAndSend(conn, req, resp);
            //5.我们这里采取的是：只要缓冲区内还有数据，就要一直进行处理！所以，为了不影响下一次的处理，我们需要手动地重置一下该连接对应的上下文 因为现在已经是被修改过的了！
            context->Reset();
            //6.根据当前连接是长连接还是短连接，判断是否要进行关闭
            if(resp.Close()) conn->Shutdown();
        }
    }

    /*根据处理结果，拼接成一个HttpResponse的格式 然后写入到缓冲区内进行发送(先启动写关心 等待发送)*/
    /*其实就是类似于序列化的操作*/
    void WriteResponseAndSend(const ConnPtr& conn, HttpRequest& req, HttpResponse& resp){
        //1.完善resp应该有的字段
        (req.Close()) ? resp.SetHeaders("Connection", "close") : resp.SetHeaders("Connection", "keep-alive");
        if(resp._text.size() && !resp.HasHeaders("Content-Legnth")) resp.SetHeaders("Content-Legnth", std::to_string(resp._text.size()));
        if(resp._text.size() && !resp.HasHeaders("Content-Type")) resp.SetHeaders("Content-Type", "application/octet-stream");  //有长度但是没有设置类型字段，就给一个二进制流
        if(resp._is_redirect) resp.SetHeaders("Location", resp._redirect_location);
        //2.将rsp的要素，按照http协议的格式进行组织
        std::stringstream ss;
        ss << req._version << " " << resp._statu_code << " " << Util::StatuDesc(resp._statu_code) << "\r\n";
        for(auto& head : resp._headers){
            ss << head.first << ": " << head.second << "\r\n";
        }
        ss << "\r\n";
        ss << resp._text;
        std::string tmp = ss.str();
        //3.发送
        conn->Send((char*)tmp.c_str(), tmp.size());
    }

    void EnableInactiveRelease(int timeout){_server.EnableInactiveRelease(timeout);}        /*设置超时任务的启动*/
public:
    HttpServer(uint32_t port, int timeout = DEFAULT_TIMEOUT):_server(port)
    {
        _server.SetConnectedCallback(std::bind(&HttpServer::OnConnected, this, std::placeholders::_1));
        _server.SetMessageCallback(std::bind(&HttpServer::OnMessage, this, std::placeholders::_1, std::placeholders::_2));
        _server.EnableInactiveRelease(timeout);
    }
    ~HttpServer(){}

    void SetThreadNum(int num){_server.SetLoopThreadNum(num);}                              /*设置线程池中线程个数*/

    /*设置当前站点的根目录*/
    void SetBaseRootDir(const std::string& base_root_dir){
        _base_root_dir = base_root_dir;
        if(_base_root_dir.back() != '/') _base_root_dir += '/'; //保证站点根目录是：./wwwroot/
    }  

    /*提供接口 以便让组件使用者能够设置路由*/
    /*插入的都是正则表达式！取决于组件使用者*/
    void GetRouteSet(const std::string& pattern, const Handler& handler){_get_route.push_back({std::regex(pattern), handler});}
    void PostRouteSet(const std::string& pattern, const Handler& handler){_post_route.push_back({std::regex(pattern), handler});}
    void PutRouteSet(const std::string& pattern, const Handler& handler){_put_route.push_back({std::regex(pattern), handler});}
    void DeleteRouteSet(const std::string& pattern, const Handler& handler){_delete_route.push_back({std::regex(pattern), handler});}

    /*启动服务器*/
    void Listen(){_server.Start();}

private:
//一些路由表，如果请求的是一些动态服务的话 第一个存储的是正则表达式！
    HandlerRoute _get_route;
    HandlerRoute _post_route;
    HandlerRoute _put_route;
    HandlerRoute _delete_route;

    std::string _base_root_dir;         //当前站点根目录
    TcpServer _server;                  //服务器模块
};